/*
 * File:    ServiceHosts.java
 * Created: 02-Jan-2007
 * Version: $Id$
 *
 * COPYRIGHT (C) 2007, Bitgate Software, LLC.  All Rights Reserved.
 *
 * kenji.hollis@bitgatesoftware.com
 */

package com.bitgate.util.service;

import static com.bitgate.util.debug.Debug.*;

import java.util.ArrayList;

import com.bitgate.util.config.Config;
import com.bitgate.util.socket.HostMatch;

/**
 * This class allows for a mechanism to &quot;transparently&quot; check an IP address against a host allow/deny list upon
 * creation of a new connection to the system.  Depending on the service matched (by bound IP and port), the IP address
 * is checked against a set of host allow/deny patterns, and allowed or denied based on the patterns therein.  The service
 * container object (in the XML) must contain a host allow/deny object, otherwise, this ruleset will not be applied.
 *
 * @author Kenji Hollis &lt;kenji.hollis@bitgatesoftware.com&gt;
 * @version $Id$
 */
public class ServiceHosts
{
    private final ArrayList<String> allowList;
    private final ArrayList<String> denyList;
    
    /** Host type lookup methods. */
    private enum HostType {
    	/** Allow-first matching. */
    	ALLOW_FIRST,
    	
    	/** Deny-first matching. */
    	DENY_FIRST
    }
    
    private HostType hostType;

    /**
     * Constructor.  Takes the name of the service being started, so the XML configuration can be parsed.
     *
     * @param serviceName The name of the service being started.
     * @throws ServiceHostsException if the service hosts cannot be found.
     */
    public ServiceHosts(String serviceName) throws ServiceHostsException
    {
    	String serviceHostRoot = "/services/object[@name='" + serviceName + "']/object[@name='service.hosts']/";

    	this.allowList = new ArrayList<String>();
    	this.denyList = new ArrayList<String>();
    	this.hostType = null;

		String matchPattern = Config.getDefault().parseConfig("services", serviceHostRoot +
		    "property[@name='host.match-pattern']/@value");

		if (matchPattern != null) {
		    this.hostType = ("allow-first".equalsIgnoreCase(matchPattern)) ? HostType.ALLOW_FIRST : HostType.DENY_FIRST;
	
		    String numAllowText = Config.getDefault().parseConfig("services", "count(" + serviceHostRoot +
				"property[@name='host.allow'])");
		    String numDenyText = Config.getDefault().parseConfig("services", "count(" + serviceHostRoot +
				"property[@name='host.deny'])");
		    int numAllow = 0, numDeny = 0;
	
		    if (numAllowText != null && !"".equals(numAllowText)) {
		    	numAllow = Integer.parseInt(numAllowText);
		    }
	
		    if (numDenyText != null && !"".equals(numDenyText)) {
		    	numDeny = Integer.parseInt(numDenyText);
		    }
	
		    for(int i = 1; i < (numAllow + 1); i++) {
				String hostAllow = Config.getDefault().parseConfig("services", serviceHostRoot +
				    "property[@name='host.allow'][" + i + "]/@value");
		
				this.allowList.add(hostAllow);
				
				if (isDebugEnabled()) {
					debug("Service '" + serviceName + "' added host allow '" + hostAllow + "'");
				}
		    }
	
		    for(int i = 1; i < (numDeny + 1); i++) {
				String hostDeny = Config.getDefault().parseConfig("services", serviceHostRoot +
				    "property[@name='host.deny'][" + i + "]/@value");
		
				this.denyList.add(hostDeny);
				
				if (isDebugEnabled()) {
					debug("Service '" + serviceName + "' added host deny '" + hostDeny + "'");
				}
		    }
	
		    if (isDebugEnabled()) {
		    	debug("Service Name '" + serviceName + "' match pattern '" + this.hostType + "' allows '" + numAllow + "' denies '" +
		    		numDeny + "'");
		    }
		} else {
		    throw new ServiceHostsException("No host entry found for service name '" + serviceName + "'");
		}
    }

    /**
     * Determines whether or not the IP address specified is allowed to connect.
     *
     * @param ipAddress The IP address to check.
     * @return <code>true</code> if the IP address is allowed, <code>false</code> otherwise.
     */
    public boolean hostAllowed(String ipAddress)
    {
    	switch(this.hostType) {
    		case ALLOW_FIRST:
    		    return HostMatch.checkAllowDeny(ipAddress, this.allowList, this.denyList);
    		    
    		case DENY_FIRST:
    		    return HostMatch.checkDenyAllow(ipAddress, this.allowList, this.denyList);
    		    
    		default:
    			throw new RuntimeException("Unknown match type '" + this.hostType + "'");
    	}
    }
}
